---
import type { MarkdownHeading } from "astro";
import { siteConfig } from "@/config/siteConfig";
import { url } from "@/utils/url-utils";

//已弃用，使用FloatingTOC.astro代替

interface Props {
  class?: string;
  headings: MarkdownHeading[];
}

let { headings = [] } = Astro.props;

let minDepth = 10;
for (const heading of headings) {
  minDepth = Math.min(minDepth, heading.depth);
}

const className = Astro.props.class;
// 修复路由判断逻辑，确保在文章页面能正确显示TOC
const isPostsRoute =
  Astro.url.pathname.includes("/posts/") || headings.length > 0;

const removeTailingHash = (text: string) => {
  let lastIndexOfHash = text.lastIndexOf("#");
  if (lastIndexOfHash !== text.length - 1) {
    return text;
  }

  return text.substring(0, lastIndexOfHash);
};

let heading1Count = 1;

const maxLevel = siteConfig.toc.depth;
---

<table-of-contents class:list={[className, "group"]} id="toc">
  <!-- TOC内容将由JavaScript动态生成 -->
</table-of-contents>

<script>
  class TableOfContents extends HTMLElement {
    tocEl: HTMLElement | null = null;
    visibleClass = "visible";
    observer: IntersectionObserver;
    anchorNavTarget: HTMLElement | null = null;
    headingIdxMap = new Map<string, number>();
    headings: HTMLElement[] = [];
    sections: HTMLElement[] = [];
    tocEntries: HTMLAnchorElement[] = [];
    active: boolean[] = [];
    activeIndicator: HTMLElement | null = null;

    constructor() {
      super();
      this.observer = new IntersectionObserver(this.markVisibleSection, {
        threshold: 0,
      });
    }

    markVisibleSection = (entries: IntersectionObserverEntry[]) => {
      entries.forEach((entry) => {
        const id = entry.target.children[0]?.getAttribute("id");
        const idx = id ? this.headingIdxMap.get(id) : undefined;
        if (idx != undefined) this.active[idx] = entry.isIntersecting;

        if (
          entry.isIntersecting &&
          this.anchorNavTarget == entry.target.firstChild
        )
          this.anchorNavTarget = null;
      });

      if (!this.active.includes(true)) this.fallback();
      this.update();
    };

    toggleActiveHeading = () => {
      let i = this.active.length - 1;
      let min = this.active.length - 1,
        max = -1;
      while (i >= 0 && !this.active[i]) {
        this.tocEntries[i].classList.remove(this.visibleClass);
        i--;
      }
      while (i >= 0 && this.active[i]) {
        this.tocEntries[i].classList.add(this.visibleClass);
        min = Math.min(min, i);
        max = Math.max(max, i);
        i--;
      }
      while (i >= 0) {
        this.tocEntries[i].classList.remove(this.visibleClass);
        i--;
      }
      if (min > max) {
        this.activeIndicator?.setAttribute("style", `opacity: 0`);
      } else {
        let parentOffset = this.tocEl?.getBoundingClientRect().top || 0;
        let scrollOffset = this.tocEl?.scrollTop || 0;
        let top =
          this.tocEntries[min].getBoundingClientRect().top -
          parentOffset +
          scrollOffset;
        let bottom =
          this.tocEntries[max].getBoundingClientRect().bottom -
          parentOffset +
          scrollOffset;
        this.activeIndicator?.setAttribute(
          "style",
          `top: ${top}px; height: ${bottom - top}px`
        );
      }
    };

    scrollToActiveHeading = () => {
      // If the TOC widget can accommodate both the topmost
      // and bottommost items, scroll to the topmost item.
      // Otherwise, scroll to the bottommost one.

      if (this.anchorNavTarget || !this.tocEl) return;
      const activeHeading = document.querySelectorAll<HTMLDivElement>(
        `#toc .${this.visibleClass}`
      );
      if (!activeHeading.length) return;

      const topmost = activeHeading[0];
      const bottommost = activeHeading[activeHeading.length - 1];
      const tocHeight = this.tocEl.clientHeight;

      let top;
      if (
        bottommost.getBoundingClientRect().bottom -
          topmost.getBoundingClientRect().top <
        0.9 * tocHeight
      )
        top = topmost.offsetTop - 32;
      else top = bottommost.offsetTop - tocHeight * 0.8;

      this.tocEl.scrollTo({
        top,
        left: 0,
        behavior: "smooth",
      });
    };

    update = () => {
      requestAnimationFrame(() => {
        this.toggleActiveHeading();
        // requestAnimationFrame(() => {
        this.scrollToActiveHeading();
        // });
      });
    };

    fallback = () => {
      if (!this.sections.length) return;

      for (let i = 0; i < this.sections.length; i++) {
        let offsetTop = this.sections[i].getBoundingClientRect().top;
        let offsetBottom = this.sections[i].getBoundingClientRect().bottom;

        if (
          this.isInRange(offsetTop, 0, window.innerHeight) ||
          this.isInRange(offsetBottom, 0, window.innerHeight) ||
          (offsetTop < 0 && offsetBottom > window.innerHeight)
        ) {
          this.markActiveHeading(i);
        } else if (offsetTop > window.innerHeight) break;
      }
    };

    markActiveHeading = (idx: number) => {
      this.active[idx] = true;
    };

    handleAnchorClick = (event: Event) => {
      const anchor = event
        .composedPath()
        .find((element) => element instanceof HTMLAnchorElement);

      if (anchor) {
        event.preventDefault(); // 阻止默认的锚点跳转

        const id = decodeURIComponent(anchor.hash?.substring(1));
        const targetElement = document.getElementById(id);

        if (targetElement) {
          // 计算目标位置，与移动端保持一致
          const navbarHeight = 80; // 导航栏高度
          const targetTop =
            targetElement.getBoundingClientRect().top +
            window.pageYOffset -
            navbarHeight;

          // 使用与移动端相同的滚动方式
          window.scrollTo({
            top: targetTop,
            behavior: "smooth",
          });
        }

        const idx = this.headingIdxMap.get(id);
        if (idx !== undefined) {
          this.anchorNavTarget = this.headings[idx];
        } else {
          this.anchorNavTarget = null;
        }
      }
    };

    isInRange(value: number, min: number, max: number) {
      return min < value && value < max;
    }

    connectedCallback() {
      // 优先监听动画结束，兜底定时初始化，确保二次刷新也能正常显示 TOC
      const element =
        document.querySelector(".custom-md") ||
        document.querySelector(".prose") ||
        document.querySelector(".markdown-content");
      let initialized = false;
      const tryInit = () => {
        if (!initialized) {
          initialized = true;
          this.init();
        }
      };
      if (element) {
        element.addEventListener("animationend", tryInit, { once: true });
        // 兜底：无论动画是否触发，300ms后强制初始化一次
        setTimeout(tryInit, 300);
      } else {
        // 没有动画元素，直接初始化，兜底再延迟一次
        tryInit();
        setTimeout(tryInit, 300);
      }
    }

    init() {
      // 重新生成TOC内容
      this.regenerateTOC();

      this.tocEl = this;

      this.tocEl.addEventListener("click", this.handleAnchorClick, {
        capture: true,
      });

      this.activeIndicator = document.getElementById("active-indicator");

      this.tocEntries = Array.from(
        this.querySelectorAll<HTMLAnchorElement>("a[href^='#']")
      );

      if (this.tocEntries.length === 0) return;

      this.sections = new Array(this.tocEntries.length);
      this.headings = new Array(this.tocEntries.length);
      for (let i = 0; i < this.tocEntries.length; i++) {
        const id = decodeURIComponent(this.tocEntries[i].hash?.substring(1));
        const heading = document.getElementById(id);
        const section = heading?.parentElement;
        if (heading instanceof HTMLElement && section instanceof HTMLElement) {
          this.headings[i] = heading;
          this.sections[i] = section;
          this.headingIdxMap.set(id, i);
        }
      }
      this.active = new Array(this.tocEntries.length).fill(false);

      this.sections.forEach((section) => this.observer.observe(section));

      this.fallback();
      this.update();
    }

    regenerateTOC(retryCount = 0) {
      // 检查是否为文章页面
      const isPostPage =
        window.location.pathname.includes("/posts/") ||
        document.querySelector(".custom-md, .markdown-content") !== null;
      if (!isPostPage) {
        // 如果不是文章页面，隐藏TOC
        this.innerHTML = "";
        return;
      }
      // 从当前页面重新获取标题
      const headings = Array.from(
        document.querySelectorAll("h1, h2, h3, h4, h5, h6")
      )
        .filter((h) => h.id)
        .map((h) => ({
          depth: parseInt(h.tagName.substring(1)),
          slug: h.id,
          text: (h.textContent || "").replace(/#+\s*$/, ""),
        }));
      // 如果没有标题，延迟重试最多3次
      if (headings.length === 0 && retryCount < 3) {
        setTimeout(() => this.regenerateTOC(retryCount + 1), 120);
        return;
      }
      if (headings.length === 0) {
        this.innerHTML = "";
        return;
      }
      // 重新生成TOC HTML
      const minDepth = Math.min(...headings.map((h) => h.depth));
      const maxLevel = 3; // siteConfig.toc.depth
      let heading1Count = 1;
      const tocHTML = headings
        .filter((heading) => heading.depth < minDepth + maxLevel)
        .map((heading) => {
          const depthClass =
            heading.depth === minDepth
              ? ""
              : heading.depth === minDepth + 1
                ? "ml-4"
                : "ml-8";
          const badgeContent =
            heading.depth === minDepth
              ? heading1Count++
              : heading.depth === minDepth + 1
                ? '<div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>'
                : '<div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>';
          return `<a href="#${heading.slug}" class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2">
                    <div class="transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold ${depthClass} ${heading.depth === minDepth ? "bg-[var(--toc-badge-bg)] text-[var(--btn-content)]" : ""}">
                        ${badgeContent}
                    </div>
                    <div class="transition text-sm ${heading.depth <= minDepth + 1 ? "text-50" : "text-30"}">${heading.text}</div>
                </a>`;
        })
        .join("");
      this.innerHTML =
        tocHTML +
        '<div id="active-indicator" style="opacity: 0" class="-z-10 absolute bg-[var(--toc-btn-hover)] left-0 right-0 rounded-xl transition-all group-hover:bg-transparent border-2 border-[var(--toc-btn-hover)] group-hover:border-[var(--toc-btn-active)] border-dashed"></div>';
    }

    disconnectedCallback() {
      this.sections.forEach((section) => this.observer.unobserve(section));
      this.observer.disconnect();
      this.tocEl?.removeEventListener("click", this.handleAnchorClick);
    }
  }

  if (!customElements.get("table-of-contents")) {
    customElements.define("table-of-contents", TableOfContents);
  }
</script>
